package com.YHVision.qiuzhike.controller;

import cn.dev33.satoken.annotation.SaCheckRole;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.YHVision.qiuzhike.manager.AIManager;
import com.YHVision.qiuzhike.model.dto.chart.ChartAiQuestionContentDTO;
import com.YHVision.qiuzhike.model.dto.chart.ChartGenerateQueryRequest;
import com.YHVision.qiuzhike.model.dto.question.QuestionEsDTO;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.YHVision.qiuzhike.annotation.AuthCheck;
import com.YHVision.qiuzhike.common.BaseResponse;
import com.YHVision.qiuzhike.common.DeleteRequest;
import com.YHVision.qiuzhike.common.ErrorCode;
import com.YHVision.qiuzhike.common.ResultUtils;
import com.YHVision.qiuzhike.constant.UserConstant;
import com.YHVision.qiuzhike.exception.BusinessException;
import com.YHVision.qiuzhike.exception.ThrowUtils;
import com.YHVision.qiuzhike.model.dto.chart.ChartAddRequest;
import com.YHVision.qiuzhike.model.dto.chart.ChartQueryRequest;
import com.YHVision.qiuzhike.model.entity.Chart;
import com.YHVision.qiuzhike.model.entity.User;
import com.YHVision.qiuzhike.model.vo.ChartVO;
import com.YHVision.qiuzhike.service.ChartService;
import com.YHVision.qiuzhike.service.UserService;
import com.zhipu.oapi.service.v4.model.ModelData;
import freemarker.template.utility.StringUtil;
import io.reactivex.Flowable;
import io.reactivex.schedulers.Schedulers;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * AI助手接口
 *
 * @author <a href="">YHVision-小y</a>
 * @from <a href="todo">爱编程学习圈</a>
 */
@RestController
@RequestMapping("/chart")
@Slf4j
public class ChartController {

    @Resource
    private ChartService chartService;

    @Resource
    private UserService userService;

    @Resource
    private AIManager aiManager;


    // 系统预设
    private static final String GENERATE_QUESTION_SYSTEM_MESSAGE = "你是一个严谨的高级工程师和一个出题专家，你擅长Java，Python，Go，React，Vue，Html，我会给你如下信息：\n" +
            "```\n" +
            "专业技能，\n" +
            "【【【专业技能描述】】】，\n" +
            "```\n" +
            "项目描述，\n" +
            "【【【项目描述】】】，\n" +
            "```\n" +
            "工作内容，\n" +
            "【【【工作内容描述】】】，\n" +
            "```\n" +
            "\n" +
            "请你根据上述技能描述和项目经历描述，按照以下步骤来出题：\n" +
            "1.要求：题目个数要求20个，结合专业技能描述出10个题，你要先理解项目描述结合工作内容描述出10个题，这10个题要结合一定的场景，题目字数不能超过50个字，题目不能包含序号，题目不能重复\n" +
            "2.严格注意出专业技能相关的10个题只能和专业技能相关，出工作内容相关的10个题只能和工作内容相关，\n" +
            "3.严格按照下面的json格式输出题目和结果\n" +
            "```\n" +
            "[{\"title\":\"题目标题\",\"value\":\"题目答案\",\"suggestion\":\"学习建议\"}]\n" +
            "```\n" +
            "title是题目，value是答案，suggestion是学习建议，\n" +
            "4. 检查题目是否包含序号，若包含序号则去除序号\n" +
            "5. 返回的题目列表格式必须为 JSON 数组";

    // region 增删改查

    /**
     * 创建AI助手
     *
     * @param chartAddRequest
     * @param request
     * @return
     */
    @PostMapping("/add")
    public BaseResponse<Long> addChart(@RequestBody ChartAddRequest chartAddRequest, HttpServletRequest request) {
        ThrowUtils.throwIf(chartAddRequest == null, ErrorCode.PARAMS_ERROR);
        // todo 在此处将实体类和 DTO 进行转换
        Chart chart = new Chart();
        BeanUtils.copyProperties(chartAddRequest, chart);
        // 数据校验
        chartService.validChart(chart, true);
        // todo 填充默认值
        User loginUser = userService.getLoginUser(request);
        chart.setUserId(loginUser.getId());
        // 写入数据库
        boolean result = chartService.save(chart);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        // 返回新写入的数据 id
        long newChartId = chart.getId();
        return ResultUtils.success(newChartId);
    }

    /**
     * 删除AI助手
     *
     * @param deleteRequest
     * @param request
     * @return
     */
    @PostMapping("/delete")
    public BaseResponse<Boolean> deleteChart(@RequestBody DeleteRequest deleteRequest, HttpServletRequest request) {
        if (deleteRequest == null || deleteRequest.getId() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        User user = userService.getLoginUser(request);
        long id = deleteRequest.getId();
        // 判断是否存在
        Chart oldChart = chartService.getById(id);
        ThrowUtils.throwIf(oldChart == null, ErrorCode.NOT_FOUND_ERROR);
        // 仅本人或管理员可删除
        if (!oldChart.getUserId().equals(user.getId()) && !userService.isAdmin(request)) {
            throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
        }
        // 操作数据库
        boolean result = chartService.removeById(id);
        ThrowUtils.throwIf(!result, ErrorCode.OPERATION_ERROR);
        return ResultUtils.success(true);
    }

    /**
     * 根据 id 获取AI助手（封装类）
     *
     * @param id
     * @return
     */
    @GetMapping("/get/vo")
    public BaseResponse<ChartVO> getChartVOById(long id, HttpServletRequest request) {
        ThrowUtils.throwIf(id <= 0, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Chart chart = chartService.getById(id);
        ThrowUtils.throwIf(chart == null, ErrorCode.NOT_FOUND_ERROR);
        // 获取封装类
        return ResultUtils.success(chartService.getChartVO(chart, request));
    }

    /**
     * 分页获取AI助手列表（仅管理员可用）
     *
     * @param chartQueryRequest
     * @return
     */
    @PostMapping("/list/page")
    @SaCheckRole(UserConstant.ADMIN_ROLE)
    public BaseResponse<Page<Chart>> listChartByPage(@RequestBody ChartQueryRequest chartQueryRequest) {
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        // 查询数据库
        Page<Chart> chartPage = chartService.page(new Page<>(current, size),
                chartService.getQueryWrapper(chartQueryRequest));
        return ResultUtils.success(chartPage);
    }

    /**
     * 分页获取AI助手列表（封装类）
     *
     * @param chartQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/list/page/vo")
    public BaseResponse<Page<ChartVO>> listChartVOByPage(@RequestBody ChartQueryRequest chartQueryRequest,
                                                         HttpServletRequest request) {
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Page<Chart> chartPage = chartService.page(new Page<>(current, size),
                chartService.getQueryWrapper(chartQueryRequest));
        // 获取封装类
        return ResultUtils.success(chartService.getChartVOPage(chartPage, request));
    }

    /**
     * 分页获取当前登录用户创建的AI助手列表
     *
     * @param chartQueryRequest
     * @param request
     * @return
     */
    @PostMapping("/my/list/page/vo")
    public BaseResponse<Page<ChartVO>> listMyChartVOByPage(@RequestBody ChartQueryRequest chartQueryRequest,
                                                           HttpServletRequest request) {
        ThrowUtils.throwIf(chartQueryRequest == null, ErrorCode.PARAMS_ERROR);
        // 补充查询条件，只查询当前登录用户的数据
        User loginUser = userService.getLoginUser(request);
        chartQueryRequest.setUserId(loginUser.getId());
        long current = chartQueryRequest.getCurrent();
        long size = chartQueryRequest.getPageSize();
        // 限制爬虫
        ThrowUtils.throwIf(size > 20, ErrorCode.PARAMS_ERROR);
        // 查询数据库
        Page<Chart> chartPage = chartService.page(new Page<>(current, size),
                chartService.getQueryWrapper(chartQueryRequest));
        // 获取封装类
        return ResultUtils.success(chartService.getChartVOPage(chartPage, request));
    }

    /**
     * 用户提供的信息
     *
     * @param skill
     * @param project
     * @param work
     * @return
     */
    private String getGenerateUserQuestionMassage(String skill, String project, String work) {
        StringBuilder userMassages = new StringBuilder();
        userMassages.append(skill).append("\n");
        userMassages.append(project).append("\n");
        userMassages.append(work);
        return userMassages.toString();
    }

    /**
     * 获取AI生成的内容（同步）
     *
     * @param chartGenerateQueryRequest
     * @return
     */
    @PostMapping("/get/ai/question/answer")
    public BaseResponse<List<ChartAiQuestionContentDTO>> getAiQuestion(@RequestBody ChartGenerateQueryRequest chartGenerateQueryRequest) {
        //数据校验
        ThrowUtils.throwIf(chartGenerateQueryRequest == null, ErrorCode.PARAMS_ERROR);

        // 获取需要传给AI的数据
        String skill = chartGenerateQueryRequest.getSkill();
        String project = chartGenerateQueryRequest.getProject();
        String work = chartGenerateQueryRequest.getWork();
        //封装prompt
        String userMassage = getGenerateUserQuestionMassage(skill, project, work);
        //将封装的prompt传给AI,获取AI返回的数据
        String result = aiManager.doSyncStableRequest(GENERATE_QUESTION_SYSTEM_MESSAGE, userMassage);
//        json
//        [
//        {"title":"使用Java反射机制读取自定义注解信息", "value":"反射API读取注解", "suggestion":"深入学习Java反射和注解"},
//        {"title":"Hutool工具库中的CollUtil类用于什么操作", "value":"集合操作，如转换、过滤", "suggestion":"熟悉Hutool工具库"},
//        {"title":"如何使用Easy Excel处理大数据量导出", "value":"使用Easy Excel进行大数据量导出", "suggestion":"学习Easy Excel的高级用法"},
//        {"title":"SpringBoot中如何使用Logback进行日志管理", "value":"配置logback.xml", "suggestion":"掌握Logback配置"},
//        {"title":"编写一个高效的SQL查询语句并解释索引优化", "value":"高效SQL查询示例", "suggestion":"了解索引优化技巧"},
//        {"title":"Redisson中如何使用BitMap实现用户签到", "value":"Redisson BitMap实现", "suggestion":"了解Redisson的高级功能"},
//        {"title":"Elasticsearch中如何配置分词器", "value":"分词器配置示例", "suggestion":"学习Elasticsearch分词"},
//        {"title":"简述SSE和RxJava在长连接推送中的应用", "value":"SSE和RxJava结合使用", "suggestion":"理解SSE和RxJava"},
//        {"title":"多级缓存策略中如何使用Redis和JVM缓存", "value":"缓存策略设计", "suggestion":"学习缓存设计原则"},
//        {"title":"解释RBAC鉴权系统的基本原理", "value":"基于角色的访问控制", "suggestion":"了解企业安全策略"}
//        ]
        //结果处理
        int start = result.indexOf("[");
        int end = result.lastIndexOf("]");
        String json = result.substring(start, end + 1);
        List<ChartAiQuestionContentDTO> ChartAiQuestionContentDTOList = JSONUtil.toList(JSONUtil.parseArray(json), ChartAiQuestionContentDTO.class);
        return ResultUtils.success(ChartAiQuestionContentDTOList);
    }

    /**
     * 获取AI生成的内容(流式)
     *
     * @param chartGenerateQueryRequest
     * @return
     */
    @GetMapping("/get/ai/question/answer/SSE")
    public SseEmitter getAiQuestionSSE(ChartGenerateQueryRequest chartGenerateQueryRequest) {
        //数据校验
        ThrowUtils.throwIf(chartGenerateQueryRequest == null, ErrorCode.PARAMS_ERROR);

        // 获取需要传给AI的数据
        String skill = chartGenerateQueryRequest.getSkill();
        String project = chartGenerateQueryRequest.getProject();
        String work = chartGenerateQueryRequest.getWork();
        //封装prompt
        String userMassage = getGenerateUserQuestionMassage(skill, project, work);
        // 建立SSE连接对象，超时时间设置为用不超时：0
        SseEmitter sseEmitter = new SseEmitter(0L);
        // 定义一个整形，用于括号计数器，当回归为0时，表示左括号匹配右口号，但要要保证原子性
        AtomicInteger count = new AtomicInteger(0);
        //将封装的prompt传给AI,获取AI返回的数据
        Flowable<ModelData> dataFlowable = aiManager.doStreamRequest(GENERATE_QUESTION_SYSTEM_MESSAGE, userMassage, null);
        StringBuilder stringBuilder = new StringBuilder();
//        json
//        [
//        {"title":"使用Java反射机制读取自定义注解信息", "value":"反射API读取注解", "suggestion":"深入学习Java反射和注解"},
//        {"title":"Hutool工具库中的CollUtil类用于什么操作", "value":"集合操作，如转换、过滤", "suggestion":"熟悉Hutool工具库"},
//        {"title":"如何使用Easy Excel处理大数据量导出", "value":"使用Easy Excel进行大数据量导出", "suggestion":"学习Easy Excel的高级用法"},
//        {"title":"SpringBoot中如何使用Logback进行日志管理", "value":"配置logback.xml", "suggestion":"掌握Logback配置"},
//        {"title":"编写一个高效的SQL查询语句并解释索引优化", "value":"高效SQL查询示例", "suggestion":"了解索引优化技巧"},
//        {"title":"Redisson中如何使用BitMap实现用户签到", "value":"Redisson BitMap实现", "suggestion":"了解Redisson的高级功能"},
//        {"title":"Elasticsearch中如何配置分词器", "value":"分词器配置示例", "suggestion":"学习Elasticsearch分词"},
//        {"title":"简述SSE和RxJava在长连接推送中的应用", "value":"SSE和RxJava结合使用", "suggestion":"理解SSE和RxJava"},
//        {"title":"多级缓存策略中如何使用Redis和JVM缓存", "value":"缓存策略设计", "suggestion":"学习缓存设计原则"},
//        {"title":"解释RBAC鉴权系统的基本原理", "value":"基于角色的访问控制", "suggestion":"了解企业安全策略"}
//        ]
        dataFlowable
                .observeOn(Schedulers.io())
                .map(modelData -> modelData.getChoices().get(0).getDelta().getContent()) //获取完整字符串，包括空格
                .map(massage -> massage.replaceAll("\\s", "")) //用正则表达式将空格转化为字符
                .filter(StrUtil::isNotBlank)
                .flatMap(massage -> {
                    ArrayList<Character> characterList = new ArrayList<>();
                    for (char c : massage.toCharArray()) {
                        characterList.add(c);
                    }// 循环将单个字符放到枚举列表中
                    return Flowable.fromIterable(characterList);
                })//过滤掉空字符串
                .doOnNext(c -> {
                    if (c == '{') {
                        count.addAndGet(1);
                    }
                    if (count.get() > 0) {
                        stringBuilder.append(c);
                    }
                    if (c == '}') {
                        count.addAndGet(-1);
                        if (count.get() == 0) {
                            // 拼接完整题目
                            sseEmitter.send(JSONUtil.toJsonStr(stringBuilder.toString()));
                            // 清空字符串，然后拼接下一道题
                            stringBuilder.setLength(0);
                        }
                    }
                })
                .doOnError((e) -> log.error("sse error", e))
                .doOnComplete(() -> {
                    log.info("sse complete");
                    sseEmitter.complete();
                })
                .subscribe();

        return sseEmitter;
    }

    // endregion
}
